#!/usr/bin/env python3
# -*- python -*-
#
# Copyright (C) 2019 Red Hat, Inc
#
# QEMU SystemTap Trace Tool
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.

import argparse
import copy
import os.path
import re
import subprocess
import sys


def probe_prefix(binary):
    dirname, filename = os.path.split(binary)
    return re.sub("-", ".", filename) + ".log"


def which(binary):
    for path in os.environ["PATH"].split(os.pathsep):
        if os.path.exists(os.path.join(path, binary)):
                return os.path.join(path, binary)

    print("Unable to find '%s' in $PATH" % binary)
    sys.exit(1)


def tapset_dir(binary):
    dirname, filename = os.path.split(binary)
    if dirname == '':
        thisfile = which(binary)
    else:
        thisfile = os.path.realpath(binary)
        if not os.path.exists(thisfile):
            print("Unable to find '%s'" % thisfile)
            sys.exit(1)

    basedir = os.path.split(thisfile)[0]
    tapset = os.path.join(basedir, "..", "share", "systemtap", "tapset")
    return os.path.realpath(tapset)


def cmd_run(args):
    prefix = probe_prefix(args.binary)
    tapsets = tapset_dir(args.binary)

    if args.verbose:
        print("Using tapset dir '%s' for binary '%s'" % (tapsets, args.binary))

    probes = []
    for probe in args.probes:
        probes.append("probe %s.%s {}" % (prefix, probe))
    if len(probes) == 0:
        print("At least one probe pattern must be specified")
        sys.exit(1)

    script = " ".join(probes)
    if args.verbose:
        print("Compiling script '%s'" % script)
        script = """probe begin { print("Running script, <Ctrl>-c to quit\\n") } """ + script

    # We request an 8MB buffer, since the stap default 1MB buffer
    # can be easily overflowed by frequently firing QEMU traces
    stapargs = ["stap", "-s", "8", "-I", tapsets ]
    if args.pid is not None:
        stapargs.extend(["-x", args.pid])
    stapargs.extend(["-e", script])
    subprocess.call(stapargs)


def cmd_list(args):
    tapsets = tapset_dir(args.binary)

    if args.verbose:
        print("Using tapset dir '%s' for binary '%s'" % (tapsets, args.binary))

    def print_probes(verbose, name):
        prefix = probe_prefix(args.binary)
        offset = len(prefix) + 1
        script = prefix + "." + name

        if verbose:
            print("Listing probes with name '%s'" % script)
        proc = subprocess.Popen(["stap", "-I", tapsets, "-l", script],
                                stdout=subprocess.PIPE,
                                universal_newlines=True)
        out, err = proc.communicate()
        if proc.returncode != 0:
            print("No probes found, are the tapsets installed in %s" % tapset_dir(args.binary))
            sys.exit(1)

        for line in out.splitlines():
            if line.startswith(prefix):
                print("%s" % line[offset:])

    if len(args.probes) == 0:
        print_probes(args.verbose, "*")
    else:
        for probe in args.probes:
            print_probes(args.verbose, probe)


def main():
    parser = argparse.ArgumentParser(description="QEMU SystemTap trace tool")
    parser.add_argument("-v", "--verbose", help="Print verbose progress info",
                        action='store_true')

    subparser = parser.add_subparsers(help="commands")
    subparser.required = True
    subparser.dest = "command"

    runparser = subparser.add_parser("run", help="Run a trace session",
                                     formatter_class=argparse.RawDescriptionHelpFormatter,
                                     epilog="""

To watch all trace points on the qemu-system-x86_64 binary:

   %(argv0)s run qemu-system-x86_64

To only watch the trace points matching the qio* and qcrypto* patterns

   %(argv0)s run qemu-system-x86_64 'qio*' 'qcrypto*'
""" % {"argv0": sys.argv[0]})
    runparser.set_defaults(func=cmd_run)
    runparser.add_argument("--pid", "-p", dest="pid",
                           help="Restrict tracing to a specific process ID")
    runparser.add_argument("binary", help="QEMU system or user emulator binary")
    runparser.add_argument("probes", help="Probe names or wildcards",
                           nargs=argparse.REMAINDER)

    listparser = subparser.add_parser("list", help="List probe points",
                                      formatter_class=argparse.RawDescriptionHelpFormatter,
                                      epilog="""

To list all trace points on the qemu-system-x86_64 binary:

   %(argv0)s list qemu-system-x86_64

To only list the trace points matching the qio* and qcrypto* patterns

   %(argv0)s list qemu-system-x86_64 'qio*' 'qcrypto*'
""" % {"argv0": sys.argv[0]})
    listparser.set_defaults(func=cmd_list)
    listparser.add_argument("binary", help="QEMU system or user emulator binary")
    listparser.add_argument("probes", help="Probe names or wildcards",
                            nargs=argparse.REMAINDER)

    args = parser.parse_args()

    args.func(args)
    sys.exit(0)

if __name__ == '__main__':
    main()
